#include "PAInterface.h"
#include "AudioReceiver.h"

#include <iostream>
#include <SDL_thread.h>
#include <assert.h>
#include <random>
#include <math.h>



PAInterface::PAInterface() 
{

}




static int PACallback(const void*                     inputBuffer,
                          void*                           outputBuffer,
                          unsigned long                   framesPerBuffer,
                          const PaStreamCallbackTimeInfo* timeInfo,
                          PaStreamCallbackFlags           statusFlags,
                          void*                           userData )
{
	int finished = 0;

	float* output = (float*)outputBuffer;
	float* buffers = (float*)userData;

	ThePAInterface::Instance()->MixToBuffer();
 

	//std::normal_distribution<float>(0, 1);

	for(unsigned int i = 0; i < framesPerBuffer; i++)
	{	
		for(int j = 0; j < CHANNELS; j++)
		{
			float* channel = (&buffers[0] + (FRAMES_PER_BUFFER * j));
			float f = channel[i];
			*output++ = channel[i];	
			channel[i] = 0;
		}
	}
	

	
	return finished;
}

void PAInterface::MixToBuffer()
{
	MutexLocker locker(&m_mutex);
	unsigned int i, j = 0;

	unsigned int numOpenTracks = m_runningTracks.size();

	
	
		//Unsure
		/*
		for(int channel = 0; channel < CHANNELS; channel++)
		{
			m_buffers[channel][j] = 0;

		}
		*/

	for(i = 0; i < numOpenTracks; i++)
	{
		RunningTrack* track = &m_runningTracks[i];
		int length = track->m_length;
		std::vector<float>* val;
		if(!track->m_temporary)
		{
			val = m_waveTables.find(track->m_trackID)->second;
		}
		else
		{
			val = track->m_pTempTable;
		}
		float leftScaler ,rightScaler;
		leftScaler = rightScaler = 0.8f;
		if(track->m_positional)
		{
			/*PositionSound(&leftScaler, &rightScaler, *track->m_pSourceX, *track->m_pSourceY, *track->m_pSourceZ);*/
			PositionSound(&leftScaler, &rightScaler, 0,0,0);
		}
		for(j = 0; j < FRAMES_PER_BUFFER; j++)
		{			

			if(track->m_readIndex < length)
			{
				float sample;
				sample = (*val)[track->m_readIndex];
				m_buffers[0][j] += sample * leftScaler;
				m_buffers[1][j] += sample * rightScaler;
				

				/*
				for(int channel = 0; channel < CHANNELS; channel++)
				{
					sample = (*val)[track->m_readIndex];
					
					//Hard coded, need to expand for extra channels
					if(channel == 0)
					{
						sample *= leftScaler;
					}
					else if(channel == 1)
					{
						sample *= rightScaler;
					}
					m_buffers[channel][j] += sample;

				}
				*/

				//Advance read index
				track->m_readIndex++;
			}
			else
			{
				//Check if track should loop
				if(track->m_loop)
				{
					track->m_readIndex = 0;
				}
				//if not remove track from running tracks
				else
				{
					RunningTracks::iterator it = m_runningTracks.begin() + i;
					if(track->m_temporary)
					{
						delete track->m_pTempTable;
					}
					m_runningTracks.erase(it);
					
					numOpenTracks = m_runningTracks.size();
					if(numOpenTracks == 0)
					{
						break;
					}
					continue;
				}
			}

		}

	}
}

void PAInterface::PositionSound(float* leftScalar, float* rightScalar, float sourceX, float sourceY, float sourceZ)
{
	if(CHANNELS == 2)
	{
		//Temporary output until panning is working properly
		*leftScalar = 1;
		*rightScalar = 1;
			//float output;

			////output = 
			///* pi/2: l/4 cycle of a sinusoid */
			////const double piovr2 = 4.0 * atan(l.Q) * 0.5;
			////const double root2ovr2 = sqrt(2.O) * 0.5;
			///* scale position to fit Lh'e pi/2 range */
			//Vec3f diff = Vec3f(sourceX, sourceY, sourceZ) - m_pAudioReceiver->GetPos(); 
			//float angle = acos(m_pAudioReceiver->GetHeading().DotProduct(diff));
			///* scale position to fit Lh'e pi/2 range */


			//angle *= (float)PI_OVER_TWO;
			///* each channel uses a 1/4 of a cycle */
			//angle *= 0.5;
			//
			//*leftScalar = (float)ROOT_TWO_OVER_TWO * (cos(angle) - sin(angle) );
			//*rightScalar = (float)ROOT_TWO_OVER_TWO * (cos(angle) + sin(angle) );
		
	}
	else
	{
		//Multichannel support to be added 
		assert(0);
	}
}



void PAInterface::PlayTrack(int id, bool loop)
{
	MutexLocker lock(&m_mutex);

	//find wave table
	WaveTables::iterator it = m_waveTables.find(id);
	assert(it != m_waveTables.end());

	//Define running track parameters
	RunningTrack track;
	track.m_trackID = id;
	track.m_loop = loop;
	track.m_readIndex = 0;
	track.m_length = it->second->size();
	track.m_positional = false;

	m_runningTracks.push_back(track);
}

void PAInterface::PlayTrack(int id, bool loop, float* sourceX, float* sourceY, float* sourceZ)
{
	MutexLocker lock(&m_mutex);
	WaveTables::iterator it = m_waveTables.find(id);
	assert(it != m_waveTables.end());

	RunningTrack track;
	track.m_trackID = id;
	track.m_loop = loop;
	track.m_readIndex = 0;
	track.m_length = it->second->size();
	track.m_positional = true;
	track.m_pSourceX = sourceX;
	track.m_pSourceY = sourceY;
	track.m_pSourceZ = sourceZ;

	m_runningTracks.push_back(track);
}

void PAInterface::LoadTemporaryWaveTable(std::vector<float>* waveTable)
{
	
	MutexLocker lock(&m_mutex);

	RunningTrack track;
	track.m_readIndex = 0;
	track.m_length = waveTable->size();
	track.m_temporary = true;
	track.m_pTempTable = waveTable;

	m_runningTracks.push_back(track);
}

int PAInterface::LoadSinTable(int id, float amplitudeScalar, float frequency, float duration)
{
	MutexLocker lock(&m_mutex);

	//calculate vector size
	float fSize = m_sampleRate * duration;
	int size = (int)fSize;
	std::vector<float>* samples = new std::vector<float>;
	//Reserve vector space
	samples->reserve(size);

	float oneOverSample = (float)(1/m_sampleRate);

    for(int i = 0; i < size; i++)
	{
		//Calculate sin wave samples
		float sample = (float)(amplitudeScalar * (sin(TWO_PI * frequency * i / m_sampleRate )));
		samples->push_back(sample);

    }
	//Add vector of samples to wave tables
	m_waveTables.insert(std::make_pair(id,samples));

	return id;

	
}

bool PAInterface::Init(unsigned int sampleRate, bool interleaved)
{

	PaError error;
	error = Pa_Initialize();
	if(error != paNoError)
	{
		PAError(error);
		return false;
	}
	// Set up output device and define parameters 
	m_outStreamParameters.device = Pa_GetDefaultOutputDevice();
    if (m_outStreamParameters.device == paNoDevice) 
	{
		PAError(error);
		return false;
	}
	m_outStreamParameters.channelCount = CHANNELS;
	m_outStreamParameters.suggestedLatency = Pa_GetDeviceInfo( m_outStreamParameters.device )->defaultLowOutputLatency;
    m_outStreamParameters.hostApiSpecificStreamInfo = NULL;

	if(interleaved)
	{
		m_outStreamParameters.sampleFormat = paFloat32;
	}
	else
	{
		m_outStreamParameters.sampleFormat = paFloat32 | paNonInterleaved;
	}

	
	 error = Pa_OpenStream(
              &m_pStream,
              NULL, /* no input */
              &m_outStreamParameters,
              sampleRate,
              FRAMES_PER_BUFFER,
              NULL,      /*no clipping */
			  PACallback, /* use callback, not blocking API */
			  &m_buffers); /* callback userData */
    if( error != paNoError )
	{
		PAError(error);
		return false;
	}
	

	m_sampleRate = sampleRate;
	
	return true;
}

bool PAInterface::StartStream()
{
	int error = Pa_StartStream(m_pStream);
	if(error != paNoError)
	{
		PAError(error);
		return false;
	}
	return true;
}

void PAInterface::StopStream()
{
	int error = Pa_StopStream(&m_pStream);
	if(error != paNoError)
	{
		PAError(error);
	}
}

void PAInterface::PAError(int error)
{
	Pa_Terminate();
	std::cout << "An error occured while using the portaudio stream\n" 
		<< "Error number: " << error << std::endl 
		<< "Error Message: " << Pa_GetErrorText(error) << std::endl;
}

void PAInterface::Work()
{
	
}

PAInterface::~PAInterface()
{

	SDL_KillThread(m_pThread);
}